package com.jeasonzhao.commons.xml;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import com.jeasonzhao.commons.utils.Algorithms;
import com.jeasonzhao.commons.utils.ConvertEx;

public class XMLNode implements java.io.Serializable
{
    private static final long serialVersionUID = 1L;

    private String m_strName = null;
    private java.util.Vector<XMLAttribute> m_attributes = null;
    private XMLNodeCollection m_subNodes = null;
    private String m_strCDATA = null;
    private String m_strInnerText = null;
    private XMLNode m_nodeParent = null;
    private boolean m_isExclusiveAttributeMode = false;
    public XMLNode setId(String str)
    {
        this.addAttribute("id",str);
        return this;
    }

    public String getCData()
    {
        return m_strCDATA == null || m_strCDATA.trim().length() < 1 ? null : m_strCDATA;
    }

    public void setCData(String str)
    {
        m_strCDATA = str;
    }

    public XMLNode(String nodeName)
    {
        this.m_strName = nodeName;
    }

    public XMLNode()
    {
    }

    public XMLNode getParentNode()
    {
        return this.m_nodeParent;
    }

    public XMLNode addNonExistedNode(String strName)
    {
        if(Algorithms.isEmpty(strName))
        {
            return this;
        }
        else
        {
            XMLNode node = this.selectNode(strName);
            if(null == node)
            {
                return this.addNode(strName);
            }
            else
            {
                return node;
            }
        }
    }

    public XMLNode addAttribute(Object ...params)
    {
        for(int n = 0;null != params && n < params.length;n += 2)
        {
            if((n + 1) < params.length && null != params[n])
            {
                this.addAttribute(new XMLAttribute(params[n].toString().trim(),params[n + 1]));
            }
        }
        return this;
    }

    public XMLNode insertNodeAt(XMLNode node,int n)
    {
        if(null == node)
        {
            return null;
        }
        if(this.m_subNodes == null)
        {
            this.m_subNodes = new XMLNodeCollection();
        }
        node.m_nodeParent = this;
        node.m_isExclusiveAttributeMode = this.m_isExclusiveAttributeMode;
        this.m_subNodes.insertElementAt(node,n);
        return node;
    }
    public XMLNode addNode(XMLNode node)
    {
        if(null == node)
        {
            return null;
        }
        if(this.m_subNodes == null)
        {
            this.m_subNodes = new XMLNodeCollection();
        }
        node.m_nodeParent = this;
        node.m_isExclusiveAttributeMode = this.m_isExclusiveAttributeMode;
        this.m_subNodes.add(node);
        return node;
    }

    public XMLNode addNode(String strNewNode)
    {
        if(null == strNewNode)
        {
            return null;
        }
        return this.addNode(new XMLNode(strNewNode));
    }

    public XMLNode addNodeAndText(String strNewNode,String innerText)
    {
        if(null == strNewNode)
        {
            return null;
        }
        return this.addNode(new XMLNode(strNewNode)).setInnerText(innerText);
    }

    public boolean isNamed(String ...names)
    {
        for(String name : names)
        {
            if(null != this.getName() && this.getName().equalsIgnoreCase(name))
            {
                return true;
            }
        }
        return false;
    }

    public XMLNode addAttribute(XMLAttribute attr)
    {
        if(null == attr)
        {
            return this;
        }
        if(this.m_attributes == null)
        {
            this.m_attributes = new Vector<XMLAttribute>();
        }
        if(this.m_isExclusiveAttributeMode)
        {
            XMLAttribute a = this.selectAttribute(attr.getName());
            if(a != null)
            {
                a.setValue(attr.getValue());
            }
            else
            {
                this.m_attributes.add(attr);
            }
        }
        else
        {
            this.m_attributes.add(attr);
        }
        return this;
    }

    public XMLNode setName(String name)
    {
        this.m_strName = name;
        return this;
    }

    public XMLNode setAttributes(Vector<XMLAttribute> attributes)
    {
        this.m_attributes = attributes;
        return this;
    }

    public XMLNode setNodes(XMLNodeCollection nodes)
    {
        this.m_subNodes = nodes;
        return this;
    }

    public XMLNode setInnerText(String innerText)
    {
        this.m_strInnerText = innerText;
        return this;
    }

    public boolean matchName(String str)
    {
        return null != this.m_strName && m_strName.equalsIgnoreCase(str);
    }

    public String getName()
    {
        return m_strName;
    }

    public XMLNode selectNode(String ...nodeNames)
    {
        XMLNodeCollection r = selectNodes(nodeNames);
        return r != null && r.size() > 0 ? r.get(0) : null;
    }

    public XMLNodeCollection selectNodes(String ...nodeNames)
    {
        if(null == nodeNames || nodeNames.length < 1 || null == m_subNodes || m_subNodes.size() < 1)
        {
            return m_subNodes == null ? new XMLNodeCollection() : m_subNodes;
        }
        XMLNodeCollection ret = new XMLNodeCollection();
        for(String strPath : nodeNames)
        {
            if(Algorithms.isEmpty(strPath))
            {
                continue;
            }
            XMLNodeCollection allParentNodes = new XMLNodeCollection();
            allParentNodes.add(this); //First node is me.
            strPath = strPath.replace('.','/');
            for(String strNodeName : strPath.split("/"))
            {
                if(strNodeName.trim().length() < 1)
                {
                    continue;
                }
                XMLNodeCollection newR = new XMLNodeCollection();
                for(XMLNode pNode : allParentNodes)
                {
                    newR.addAll(pNode.getSubNodesByName(strNodeName));
                }
                allParentNodes = newR;
                if(allParentNodes.size() < 1)
                {
                    break;
                }
            }
            ret.addAll(allParentNodes);
        }
        return ret;

    }

    public java.util.Iterator<XMLNode> iterator(String ...names)
    {
        return selectNodes(names).iterator();
    }

    private XMLNodeCollection getSubNodesByName(String ...nodeNames)
    {
        if(null == nodeNames || nodeNames.length < 1 || null == m_subNodes || m_subNodes.size() < 1)
        {
            return m_subNodes == null ? new XMLNodeCollection() : m_subNodes;
        }
        else
        {
            XMLNodeCollection ret = new XMLNodeCollection();
            for(XMLNode node : m_subNodes)
            {
                for(String s : nodeNames)
                {
                    if(null != s && s.equalsIgnoreCase(node.getName()))
                    {
                        ret.add(node);
                    }
                }
            }
            return ret;
        }
    }

    public String getInnerText()
    {
        return m_strInnerText == null || m_strInnerText.trim().length() < 1 ? null : m_strInnerText;
    }

    public String getSubInnerText()
    {
        StringBuffer buf = new StringBuffer();
        if(null != m_strInnerText)
        {
            buf.append(m_strInnerText.trim());
        }
        if(null != this.m_subNodes)
        {
            for(XMLNode node : this.m_subNodes)
            {
                buf.append(node.getSubInnerText());
            }
        }
        return buf.toString();
    }

    public String guessInnerText()
    {
        if(null != m_strInnerText)
        {
            return m_strInnerText;
        }
        if(null != this.m_subNodes)
        {
            for(XMLNode node : this.m_subNodes)
            {
                String x = node.guessInnerText();
                if(x != null)
                {
                    return x;
                }
            }
        }
        return null;
    }

    public java.util.Date dateValueOf(String strAttrName)
    {
        return ConvertEx.toDate(this.getAttributeValue(strAttrName));
    }

    public int intValueOf(String strAttrName)
    {
        return ConvertEx.toInt(this.getAttributeValue(strAttrName));
    }

    public double doubleValueOf(String strAttrName)
    {
        return ConvertEx.toDouble(this.getAttributeValue(strAttrName));
    }

    private String getAttributeValue(String ...strAttrNames)
    {
        if(strAttrNames == null)
        {
            return null;
        }
        for(String strAttrName : strAttrNames)
        {
            if(null == strAttrName || strAttrName.trim().length() < 1)
            {
                continue;
            }
            XMLAttribute attr = this.selectAttribute(strAttrName);
            if(attr == null)
            {
                continue;
            }
            else
            {
                String str = attr.getValue();
                return null == str || str.length() < 1 ? null : str;
            }
        }
        return null;
    }

    public boolean nullValueOf(String ...names)
    {
        return null == valueOf(names);
    }

    public String valueOf(String ...names)
    {
        if(null == names)
        {
            return null;
        }
        for(String strAttrName : names)
        {
            String strV = getAttributeValue(strAttrName);
            if(null != strV)
            {
                return strV;
            }
        }
        return null;
    }

    public boolean valueOfBool(String ...names)
    {
        return ConvertEx.toBool(this.valueOf(names));
    }

    public XMLAttribute selectAttribute(String ...names)
    {
        Vector<XMLAttribute> ret = this.selectAttributes(names);
        return ret == null || ret.size() < 1 ? null : ret.get(0);
    }

    public Vector<XMLAttribute> selectAttributes(String ...names)
    {
        if(null == names || this.m_attributes == null || names.length < 1)
        {
            return this.m_attributes == null ? new Vector<XMLAttribute>() : this.m_attributes;
        }
        Vector<XMLAttribute> ret = new Vector<XMLAttribute>();
        for(String strAttrName : names)
        {
            if(null == strAttrName || strAttrName.trim().length() < 1)
            {
                continue;
            }
            for(XMLAttribute attr : this.m_attributes)
            {
                if(null != attr
                   && attr.getName() != null
                   && attr.getName().equalsIgnoreCase(strAttrName))
                {
                    ret.add(attr);
                }
            }
        }
        return ret;
    }

    public String toString()
    {
        try
        {
            return XMLHelper.getXmlString(this);
        }
        catch(XMLException ex)
        {
            return this.toHtml();
        }
    }

    public List<String> subItemAndValues(String subitemsNames,String ...attributes)
    {
        return subItemAndValues(new String[]
                                {subitemsNames},attributes);
    }

    public List<String> subItemAndValues(String[] subitemsNames,String ...attributes)
    {
        ArrayList<String> ret = new ArrayList<String>();
        if(null == subitemsNames || null == attributes ||
           attributes.length < 1 || subitemsNames.length < 1)
        {
            return ret;
        }
        for(XMLNode node : this.selectNodes(subitemsNames))
        {
            String v = node.subItemOrValueOf(attributes);
            if(null != v)
            {
                ret.add(v);
            }
        }
        return ret;
    }

    public String subItemAndValue(String subitemsNames,String ...attributes)
    {
        return subItemAndValue(new String[]
                               {subitemsNames},attributes);
    }

    public String getSubItemInnerText(String ...nodeNames)
    {
        if(null == nodeNames || nodeNames.length < 1)
        {
            return null;
        }
        XMLNode node = this.selectNode(nodeNames);
        return null == node ? null : node.getInnerText();
    }

    public String subItemAndValue(String[] subitemsNames,String ...attributes)
    {
        if(null == subitemsNames || null == attributes ||
           attributes.length < 1 || subitemsNames.length < 1)
        {
            return null;
        }
        for(XMLNode node : this.selectNodes(subitemsNames))
        {
            String v = node.subItemOrValueOf(attributes);
            if(null != v)
            {
                return v;
            }
        }
        return null;
    }

    /**
     * Get value<br/>
     * <InputName value="RETURN"><br/>
     * <InputName>RETURN</InputName><br/>
     * <InputName><CDATA[[[RETURN]]]></InputName><br/>
     * <CURRENTNODE InputName="RETURN"><br/>
     * @param names String[]
     * @return String
     */
    public String subItemOrValueOf(String ...names)
    {
        if(null == names)
        {
            return null;
        }
        for(String strInitName : names)
        {
            if(null == strInitName)
            {
                continue;
            }
            String strAttributeNames = null;
            String strNodeName = strInitName;
            int npos = strInitName.indexOf("@");
            if(npos > 0)
            {
                strNodeName = strInitName.substring(0,npos).trim();
                strAttributeNames = strInitName.substring(npos + 1).trim();
            }
            String strValue = strAttributeNames == null || strAttributeNames.length() < 1
                ? this.getAttributeValue(strNodeName.split(","))
                : this.getAttributeValue(strAttributeNames.split(","));
            if(null != strValue)
            {
                return strValue;
            }
            String[] attribs = (strAttributeNames == null || strAttributeNames.length() < 1 ? "value" : strAttributeNames).split(",");
            XMLNode node = this.selectNode(strNodeName);
            if(null != node)
            {
                strValue = node.getAttributeValue(attribs);
                if(null != strValue)
                {
                    return strValue;
                }
                if(null != node.getInnerText())
                {
                    return node.m_strInnerText;
                }
                if(null != node.getCData())
                {
                    return node.getCData();
                }
            }
        }
        return null;
    }

    public boolean valueOfBool(String string,boolean b)
    {
        String strB = this.getAttributeValue(string);
        return null == strB ? b : ConvertEx.toBool(strB);
    }

    public boolean nullSubItemValue(String subItemName,String ...names)
    {
        return null == getSubItemValue(subItemName,names);
    }

    public String getSubItemValue(String subItemName,String ...names)
    {
        XMLNode node = this.selectNode(subItemName);
        return null == node ? null : node.valueOf(names);
    }

    public boolean isEmpty()
    {
        return null == this.getCData()
            && null == this.getInnerText()
            && (null == this.m_subNodes || this.m_subNodes.size() < 1)
            && (null == this.m_attributes || this.m_attributes.size() < 1);
    }

    public String getInnerHtml(boolean bQuoted)
    {
        if(Algorithms.isEmpty(this.getInnerText()) && (null == this.m_subNodes || this.m_subNodes.size() < 1))
        {
            return null;
        }
        StringBuffer buf = new StringBuffer();
        buf.append(Algorithms.notEmpty(this.getInnerText()) ? this.getInnerText() : "");
        if(null != this.m_subNodes)
        {
            for(XMLNode node : this.m_subNodes)
            {
                buf.append(node.toHtml(bQuoted));
            }
        }
        return buf.toString();
    }

    public String getPreHtml()
    {
        return this.toHtml()
            .replaceAll("<","&lt;")
            .replaceAll(">","&gt;");
    }

    public String toHtml()
    {
        return toHtml(true);
    }
    protected void prepareBeforeToHtml()
    {

    }
    public String toHtml(boolean includSubNodesHtml)
    {
        return toHtml(includSubNodesHtml,0);
    }

    private String toHtml(boolean includSubNodesHtml,int nlevel)
    {
        if(this.getName() == null || this.getName().trim().length() < 1)
        {
            return null;
        }
        prepareBeforeToHtml();
        String prefix = Algorithms.repeatString("    ",nlevel);
        final String SEP = "\"";
        String LINE = this.isNamed("SPAN","FONT","BR","P","OPTION","A","LABEL") || null != this.getCData() ? "" : "\r\n";
        StringBuffer buf = new StringBuffer();
        buf.append(prefix + "<" + this.getName());
        for(int n = 0;null != this.selectAttributes() && n < this.selectAttributes().size();n++)
        {
            XMLAttribute attr = this.selectAttributes().get(n);
            if(attr == null
               || Algorithms.isEmpty(attr.getName())
               || Algorithms.isEmpty(attr.getValue())
                )
            {
                continue;
            }
            buf.append(" ")
                .append(attr.getName())
                .append("=")
                .append(SEP)
                .append(wrapAttributeValue(attr.getValue()))
                .append(SEP);
        }
        if((this.selectNodes() == null || this.selectNodes().size() < 1) &&
           (this.m_strInnerText == null))
        {
            buf.append("/>" + LINE);
            return buf.toString();
        }
        else
        {
            buf.append(">" + LINE);
            for(int n = 0;includSubNodesHtml && null != m_subNodes && n < m_subNodes.size();n++)
            {
                XMLNode node = this.selectNodes().get(n);
                if(null != node)
                {
                    buf.append(node.toHtml(includSubNodesHtml,nlevel + 1));
                }
            }
            if(null != getCData())
            {
                buf.append(prefix + "<![CDATA[" + LINE);
                buf.append(prefix + this.getCData());
                buf.append(prefix + "]]>" + LINE);
            }
            if(null != this.m_strInnerText)
            {
                buf.append(m_strInnerText);
            }
            if(LINE.length() < 1)
            {
                buf.append("</" + this.getName() + ">" + "\r\n");
            }
            else
            {
                buf.append(prefix + "</" + this.getName() + ">" + LINE);
            }
            return buf.toString();
        }

    }

    protected String wrapAttributeValue(Object obj)
    {
        String str = Algorithms.toString(obj);
        return str
            .replaceAll("\"","&quot;")
            .replaceAll("'","&apos;")
            ;
    }

    public static XMLNode create(String strTagName,Object ...params)
    {
        XMLNode node = new XMLNode(strTagName);
        node.addAttribute(params);
        return node;
    }

    public Vector<XMLAttribute> getAttributes()
    {
        return this.m_attributes == null ? new Vector<XMLAttribute>() : m_attributes;
    }
    public XMLNode getRoot()
    {
        XMLNode node=this;
        int nx=0;
        while(node.getParentNode()!=null && nx++<10000)//For safe, it might be endless loop
        {
            node=node.getParentNode();
        }
        return node;
    }
    public Vector<XMLAttribute> getAttributesExclude(String ...names)
    {
        if(null == names || null == this.m_attributes || this.m_attributes.size() < 1)
        {
            return this.m_attributes == null ? new Vector<XMLAttribute>() : m_attributes;
        }
        String keys = "";
        for(String n : names)
        {
            if(null != n && n.trim().length() > 0)
            {
                keys += "^" + n.trim().toLowerCase();
            }
        }
        Vector<XMLAttribute> ret = new Vector<XMLAttribute>();
        for(XMLAttribute a : this.m_attributes)
        {
            if(null == a || a.getName() == null || a.getName().trim().length() < 1)
            {
                continue;
            }
            if(keys.indexOf("^" + a.getName().trim().toLowerCase()) >= 0)
            {
                continue;
            }
            else
            {
                ret.add(a);
            }
        }
        return ret;
    }
}
